﻿using System;
using System.Collections;
using System.Text;

namespace PDO.DataPersistence.Core
{
    /// <summary>
    /// Tokenize a property path string to handle simple array references, method calls and common dot notation.
    /// Used principally in conjunction with ReflectionHelper.
    /// </summary>
    public class PropertyPathTokenizer
    {
        const int MAX_PARAMETER = 10;

        private bool _isMethodCall;
        private string _methodName;
        private string _propertyName;

        private ArrayList _parameterList;
        private string _propertyPath;
        private int _position;
        private bool _inQuote;
        private bool _inParen;
        private bool _inBrace;

        StringBuilder _buffer;

        public PropertyPathTokenizer(string propertyPath)
        {
            _propertyPath = propertyPath;
            _position = 0;
            _parameterList = new ArrayList();
        }


        public bool IsMethodCall
        {
            get { return _isMethodCall; }
        }

        public string MethodName
        {
            get { return _methodName; }
        }

        public string PropertyName
        {
            get { return _propertyName; }
        }

        public ArrayList ParameterList
        {
            get { return _parameterList; }
        }

        private void AddToParameterList()
        {
            _parameterList.Add(_buffer.ToString());
            _buffer.Length = 0;

            if (_parameterList.Count >= MAX_PARAMETER)
            {
                throw new InvalidOperationException("Exceeded maximum number of parameters in method call.");
            }
        }

        public bool MoveNext()
        {
            if (_position >= _propertyPath.Length)
            {
                return false;
            }

            _inQuote = false;
            _inParen = false;
            _buffer = new StringBuilder();
            char c;
            bool exitParser = false;
            int startPosition = _position;
            for (int i = startPosition; i < _propertyPath.Length; i++)
            {
                c = _propertyPath[i];
                switch (c)
                {
                    case ' ':
                    case '\t':
                        // skip whitespace unless in quote
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        break;

                    case '\"':

                        //if (!_inParen)
                        //{
                        //    throw new InvalidOperationException("Illegal \" found; not within a parenthetical expression.");
                        //}
                        //else... 

                        if (_inQuote)
                        {
                            _inQuote = false;
                            if (_isMethodCall)
                            {
                                AddToParameterList();
                            }
                        }
                        else
                        {
                            _inQuote = true;
                        }

                        if (_inBrace)
                        {
                            // just eat quotes inside a brace for now
                            break;
                        }
                        break;

                    case '.':

                        if (_inBrace)
                        {
                            _buffer.Append(c);
                        }
                        else
                            if (!_inQuote)
                            {
                                _propertyName = _buffer.ToString();
                                _buffer.Length = 0;
                                exitParser = true;
                            }
                            else
                            {
                                _buffer.Append(c);
                            }
                        break;

                    case '(':
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        else
                        {
                            _methodName = _buffer.ToString();
                            _buffer.Length = 0;
                            _isMethodCall = true;
                            _inParen = true;
                        }
                        break;

                    case ')':
                        if (_inQuote)
                        {
                            _buffer.Append(c);
                        }
                        else
                        {
                            if (!_inParen)
                            {
                                throw new InvalidOperationException("Illegal ) found; no matching opening parenthese.");
                            }

                            _inParen = false;
                            if (_buffer.Length > 0)
                            {
                                AddToParameterList();
                            }
                        }
                        break;

                    case '[':
                        _inBrace = true;
                        _buffer.Append(c);
                        break;

                    case ']':
                        _inBrace = false;
                        _buffer.Append(c);
                        break;

                    case ',':
                        if (_inBrace)
                        {
                            _buffer.Append(c);
                        }
                        else
                            if (_inQuote)
                            {
                                _buffer.Append(c);
                            }
                            else if (!_inParen)
                            {
                                throw new InvalidOperationException("Illegal comma found; not within method parameter list.");
                            }
                            else if (_buffer.Length > 0)
                            {
                                AddToParameterList();
                            }
                        break;

                    default:
                        _buffer.Append(c);
                        break;
                }

                _position = i;

                if (exitParser)
                {
                    break;
                }
            }


            if (_inQuote)
            {
                throw new InvalidOperationException("No closing quote.");
            }
            else if (_inParen)
            {
                throw new InvalidOperationException("No closing parentheses.");
            }
            else if (_buffer.Length > 0)
            {
                // The default condition is that we were just passed a simple property name.
                _propertyName = _buffer.ToString();
            }

            _position++;

            return true;
        }
    }

}
